/**
 * TLSPRF
 *
 * An ActionScript 3 implementation of a pseudo-random generator
 * that follows the TLS specification
 * Copyright (c) 2007 Henri Torgemane
 *
 * See LICENSE.txt for full license information.
 */
package com.hurlant.crypto.prng
{
	import flash.utils.ByteArray;
	import com.hurlant.crypto.hash.HMAC;
	import com.hurlant.crypto.hash.MD5;
	import com.hurlant.crypto.hash.SHA1;
	import com.hurlant.util.Memory;
	import com.hurlant.util.Hex;
	import flash.utils.IDataOutput;

	/**
	 * There's "Random", and then there's TLS Random.
	 * .
	 * Still Pseudo-random, though.
	 */
	public class TLSPRF
	{
		// XXX WAY TOO MANY STRUCTURES HERE

		// seed
		private var seed:ByteArray;
		// P_MD5's secret
		private var s1:ByteArray;
		// P_SHA-1's secret
		private var s2:ByteArray;
		// HMAC_MD5's A
		private var a1:ByteArray;
		// HMAC_SHA1's A
		private var a2:ByteArray;
		// Pool for P_MD5
		private var p1:ByteArray;
		// Pool for P_SHA1
		private var p2:ByteArray;
		// Data for HMAC_MD5
		private var d1:ByteArray;
		// Data for HMAC_SHA1
		private var d2:ByteArray;


		private var hmac_md5:HMAC;
		private var hmac_sha1:HMAC;

		public function TLSPRF(secret:ByteArray, label:String, seed:ByteArray)
		{
			var l:int=Math.ceil(secret.length / 2);
			var s1:ByteArray=new ByteArray;
			var s2:ByteArray=new ByteArray;
			s1.writeBytes(secret, 0, l);
			s2.writeBytes(secret, secret.length - l, l);
			var s:ByteArray=new ByteArray;
			s.writeUTFBytes(label);
			s.writeBytes(seed);
			this.seed=s;
			this.s1=s1;
			this.s2=s2;

			hmac_md5=new HMAC(new MD5);
			hmac_sha1=new HMAC(new SHA1);

			this.a1=hmac_md5.compute(s1, this.seed);
			this.a2=hmac_sha1.compute(s2, this.seed);

			p1=new ByteArray;
			p2=new ByteArray;

			d1=new ByteArray;
			d2=new ByteArray;
			d1.position=MD5.HASH_SIZE;
			d1.writeBytes(this.seed);
			d2.position=SHA1.HASH_SIZE;
			d2.writeBytes(this.seed);
		}

		// XXX HORRIBLY SLOW. REWRITE.
		public function nextBytes(buffer:IDataOutput, length:int):void
		{
			while (length--)
			{
				buffer.writeByte(nextByte());
			}
		}

		public function nextByte():int
		{
			if (p1.bytesAvailable == 0)
			{
				more_md5();
			}
			if (p2.bytesAvailable == 0)
			{
				more_sha1();
			}
			return p1.readUnsignedByte() ^ p2.readUnsignedByte();
		}

		public function dispose():void
		{
			seed=dba(seed);
			s1=dba(s1);
			s2=dba(s2);
			a1=dba(a1);
			a2=dba(a2);
			p1=dba(p1);
			p2=dba(p2);
			d1=dba(d1);
			d2=dba(d2);
			hmac_md5.dispose();
			hmac_md5=null;
			hmac_sha1.dispose();
			hmac_sha1=null;
			Memory.gc();
		}

		public function toString():String
		{
			return "tls-prf";
		}

		private function dba(ba:ByteArray):ByteArray
		{
			for (var i:uint=0; i < ba.length; i++)
			{
				ba[i]=0;
			}
			ba.length=0;
			return null;
		}

		private function more_md5():void
		{
			d1.position=0;
			d1.writeBytes(a1);
			var p:int=p1.position;
			var more:ByteArray=hmac_md5.compute(s1, d1);
			a1=hmac_md5.compute(s1, a1);
			p1.writeBytes(more);
			p1.position=p;
		}

		private function more_sha1():void
		{
			d2.position=0;
			d2.writeBytes(a2);
			var p:int=p2.position;
			var more:ByteArray=hmac_sha1.compute(s2, d2);
			a2=hmac_sha1.compute(s2, a2);
			p2.writeBytes(more);
			p2.position=p;
		}

	}
}

